package org.dromara.web.service

import cn.dev33.satoken.exception.NotLoginException
import cn.dev33.satoken.stp.StpUtil
import cn.hutool.core.bean.BeanUtil
import cn.hutool.core.collection.CollUtil
import cn.hutool.core.lang.Opt
import cn.hutool.core.util.ObjectUtil
import com.baomidou.lock.annotation.Lock4j
import io.github.oshai.kotlinlogging.KotlinLogging
import me.zhyd.oauth.model.AuthUser
import org.dromara.common.core.constant.CacheConstants
import org.dromara.common.core.constant.Constants
import org.dromara.common.core.constant.SystemConstants
import org.dromara.common.core.constant.TenantConstants
import org.dromara.common.core.domain.dto.PostDTO
import org.dromara.common.core.domain.dto.RoleDTO
import org.dromara.common.core.domain.model.LoginUser
import org.dromara.common.core.enums.LoginType
import org.dromara.common.core.exception.ServiceException
import org.dromara.common.core.exception.user.UserException
import org.dromara.common.core.utils.*
import org.dromara.common.log.event.LogininforEvent
import org.dromara.common.mybatis.helper.DataPermissionHelper
import org.dromara.common.redis.utils.RedisUtils
import org.dromara.common.satoken.utils.LoginHelper
import org.dromara.common.tenant.exception.TenantException
import org.dromara.common.tenant.helper.TenantHelper
import org.dromara.system.domain.SysUser
import org.dromara.system.domain.bo.SysSocialBo
import org.dromara.system.domain.vo.*
import org.dromara.system.mapper.SysUserMapper
import org.dromara.system.service.*
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import java.time.Duration
import java.util.*
import java.util.function.Supplier

/**
 * 登录校验方法
 *
 *@author LikeYouDo
 *@date 2025/1/5 21:51
 */
@Service
class SysLoginService(
    @Value("\${user.password.maxRetryCount}")
    private var maxRetryCount: Int,

    @Value("\${user.password.lockTime}")
    private var lockTime: Long,

    private val tenantService: ISysTenantService,
    private val permissionService: ISysPermissionService,
    private val sysSocialService: ISysSocialService,
    private val roleService: ISysRoleService,
    private val deptService: ISysDeptService,
    private val postService: ISysPostService,
    private val userMapper: SysUserMapper,
) {
    companion object {
        private val log = KotlinLogging.logger {}
    }

    /**
     * 绑定第三方用户
     *
     * @param authUserData 授权响应实体
     */
    @Lock4j
    fun socialRegister(authUserData: AuthUser) {
        val authId = authUserData.source + authUserData.uuid
        // 第三方用户信息
        val bo = BeanUtil.toBean(authUserData, SysSocialBo::class.java)
        BeanUtil.copyProperties(authUserData.token, bo)
        val userId = LoginHelper.getUserId()
        bo.apply {
            this.userId = userId
            this.authId = authId
            openId = authUserData.uuid
            userName = authUserData.username
            nickName = authUserData.nickname
        }
        val checkList = sysSocialService.selectByAuthId(authId)
        if (CollUtil.isNotEmpty(checkList)) {
            throw ServiceException("此三方账号已经被绑定!")
        }
        // 查询是否已经绑定用户
        val params = SysSocialBo().apply {
            this.userId = userId
            this.source = bo.source
        }
        val list = sysSocialService.queryList(params)
        if (list.isEmpty()) {
            // 没有绑定用户, 新增用户信息
            sysSocialService.insertByBo(bo)
        } else {
            // 更新用户信息
            bo.id = list[0].id
            sysSocialService.updateByBo(bo)
            // 如果要绑定的平台账号已经被绑定过了 是否抛异常自行决断
            // throw new ServiceException("此平台账号已经被绑定!");
        }
    }


    /**
     * 退出登录
     */
    fun logout() {
        try {
            val loginUser = LoginHelper.getLoginUser<LoginUser>() ?: return
            // if (ObjectUtil.isNull(loginUser)) {
            //     return
            // }
            if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
                // 超级管理员 登出清除动态租户
                TenantHelper.clearDynamic()
            }
            recordLogininfor(
                loginUser.tenantId,
                loginUser.username,
                Constants.LOGOUT,
                MessageUtils.message("user.logout.success")
            )
        } catch (ignored: NotLoginException) {
        } finally {
            try {
                StpUtil.logout()
            } catch (ignored: NotLoginException) {
            }
        }
    }

    /**
     * 记录登录信息
     *
     * @param tenantId 租户ID
     * @param username 用户名
     * @param status   状态
     * @param message  消息内容
     */
    fun recordLogininfor(tenantId: String?, username: String?, status: String?, message: String?) =
        SpringUtils.context().publishEvent(LogininforEvent().apply {
            this.tenantId = tenantId
            this.username = username
            this.status = status
            this.message = message
            request = ServletUtils.getRequest()
        })

    /**
     * 构建登录用户
     */
    fun buildLoginUser(user: SysUserVo): LoginUser =
        LoginUser().apply {
            tenantId = user.tenantId
            userId = user.userId
            deptId = user.deptId
            username = user.userName
            nickname = user.nickName
            userType = user.userType
            menuPermission = permissionService.getMenuPermission(user.userId)
            rolePermission = permissionService.getRolePermission(user.userId)
            if (ObjectUtil.isNotNull(user.deptId)) {
                val deptOpt = Opt.of(user.deptId).map(deptService::selectDeptById)
                deptName = deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY)
                deptCategory = deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY)
            }
            roles = BeanUtil.copyToList(
                roleService.selectRolesByUserId(user.userId),
                RoleDTO::class.java
            )
            posts = BeanUtil.copyToList(
                postService.selectPostsByUserId(user.userId),
                PostDTO::class.java
            )
        }


    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    fun recordLoginInfo(userId: Long?, ip: String?) {
        val sysUser = SysUser().apply {
            this.userId = userId
            this.loginIp = ip
            loginDate = DateUtils.getNowDate()
            this.updateBy = userId
        }
        DataPermissionHelper.ignore<Int> { userMapper.updateById(sysUser) }
    }

    /**
     * 登录校验
     */
    fun checkLogin(loginType: LoginType, tenantId: String?, username: String, supplier: Supplier<Boolean>) {
        val errorKey = CacheConstants.PWD_ERR_CNT_KEY + username
        val loginFail = Constants.LOGIN_FAIL

        // 获取用户登录错误次数，默认为0 (可自定义限制策略 例如: key + username + ip)
        var errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0)
        // 锁定时间内登录 则踢出
        if (errorNumber >= maxRetryCount) {
            recordLogininfor(
                tenantId,
                username,
                loginFail,
                MessageUtils.message(loginType.retryLimitExceed, maxRetryCount, lockTime)
            )
            throw UserException(loginType.retryLimitExceed, maxRetryCount, lockTime)
        }

        if (supplier.get()) {
            // 错误次数递增
            errorNumber++
            RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime))
            // 达到规定错误次数 则锁定登录
            if (errorNumber >= maxRetryCount) {
                recordLogininfor(
                    tenantId,
                    username,
                    loginFail,
                    MessageUtils.message(loginType.retryLimitExceed, maxRetryCount, lockTime)
                )
                throw UserException(loginType.retryLimitExceed, maxRetryCount, lockTime)
            } else {
                // 未达到规定错误次数
                recordLogininfor(
                    tenantId,
                    username,
                    loginFail,
                    MessageUtils.message(loginType.retryLimitCount, errorNumber)
                )
                throw UserException(loginType.retryLimitCount, errorNumber)
            }
        }

        // 登录成功 清空错误次数
        RedisUtils.deleteObject(errorKey)
    }

    /**
     * 校验租户
     *
     * @param tenantId 租户ID
     */
    fun checkTenant(tenantId: String?) {
        if (!TenantHelper.isEnable()) {
            return
        }
        if (StringUtils.isBlank(tenantId)) {
            throw TenantException("tenant.number.not.blank")
        }
        if (TenantConstants.DEFAULT_TENANT_ID == tenantId) {
            return
        }
        val tenant = tenantService.queryByTenantId(tenantId)
        if (ObjectUtil.isNull(tenant)) {
            log.info { "登录租户：$tenantId 不存在." }
            throw TenantException("tenant.not.exists")
        } else if (SystemConstants.DISABLE == tenant.status) {
            log.info { "登录租户：$tenantId 已被停用." }
            throw TenantException("tenant.blocked")
        } else if (ObjectUtil.isNotNull(tenant.expireTime)
            && Date().after(tenant.expireTime)
        ) {
            log.info { "登录租户：$tenantId 已超过有效期." }
            throw TenantException("tenant.expired")
        }
    }
}
